package cn.lijiajia3515.cairo.security.oauth2.resource.server.web;

import cn.lijiajia3515.cairo.core.business.AuthBusiness;
import cn.lijiajia3515.cairo.security.web.authentication.CairoHttpMessageAuthenticationEntryPoint;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.server.resource.BearerTokenError;
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

@Slf4j
public class CairoBearerTokenAuthenticationEntryPoint extends CairoHttpMessageAuthenticationEntryPoint {

	public CairoBearerTokenAuthenticationEntryPoint(List<HttpMessageConverter<?>> converters) {

		super(converters, null);
	}

	public CairoBearerTokenAuthenticationEntryPoint(List<HttpMessageConverter<?>> converters, ContentNegotiationManager manager) {
		super(converters, manager);
	}

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws ServletException, IOException {
		super.commence(request, response, exception);
	}

	@Override
	protected void business(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws HttpMediaTypeNotAcceptableException, IOException {
		init();
		if (exception instanceof InsufficientAuthenticationException) {
			httpStatus = HttpStatus.UNAUTHORIZED;
			business = AuthBusiness.Required;

			this.write(this.business, exception, request, response);
		} else if (exception instanceof InvalidBearerTokenException) {
			httpStatus = HttpStatus.UNAUTHORIZED;
			business = AuthBusiness.Bad;
			if (exception.getMessage().contains("expired")) {
				business = AuthBusiness.Expired;
			}
		} else {
			super.business(request, response, exception);
		}

	}

	@Override
	public void header(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) {
		Map<String, String> parameters = new LinkedHashMap<>();
		if (this.realmName != null) {
			parameters.put("realm", this.realmName);
		}

		if (exception instanceof OAuth2AuthenticationException) {
			httpStatus = HttpStatus.UNAUTHORIZED;
			OAuth2Error error = ((OAuth2AuthenticationException) exception).getError();
			parameters.put("error", error.getErrorCode());
			if (StringUtils.hasText(error.getDescription())) {
				parameters.put("error_description", error.getDescription());
			}
			if (StringUtils.hasText(error.getUri())) {
				parameters.put("error_uri", error.getUri());
			}
			if (error instanceof BearerTokenError) {
				BearerTokenError bearerTokenError = (BearerTokenError) error;
				if (StringUtils.hasText(bearerTokenError.getScope())) {
					parameters.put("scope", bearerTokenError.getScope());
				}
				httpStatus = ((BearerTokenError) error).getHttpStatus();
			}
		}

		String wwwAuthenticate = computeWWWAuthenticateHeaderValue(parameters);
		response.addHeader(HttpHeaders.WWW_AUTHENTICATE, wwwAuthenticate);
	}

	public static String computeWWWAuthenticateHeaderValue(Map<String, String> parameters) {
		StringBuilder wwwAuthenticate = new StringBuilder();
		wwwAuthenticate.append("Bearer");
		if (!parameters.isEmpty()) {
			wwwAuthenticate.append(" ");
			int i = 0;
			for (Map.Entry<String, String> entry : parameters.entrySet()) {
				wwwAuthenticate.append(entry.getKey()).append("=\"").append(entry.getValue()).append("\"");
				if (i != parameters.size() - 1) {
					wwwAuthenticate.append(", ");
				}
				i++;
			}
		}
		return wwwAuthenticate.toString();
	}
}
